﻿// Polar coordinates math class.
// Copyright (C) 2002-2014 Nathaniel Moschkin.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.ComponentModel;
using System.Runtime;
using System.Runtime.Serialization;
using System.Runtime.InteropServices;

namespace DataTools.Math
{
    
    /// <summary>
    /// Point structure.
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    public struct Point
    {
        /// <summary>
        /// X coordinate.
        /// </summary>
        public int X;

        /// <summary>
        /// Y coordinate.
        /// </summary>
        public int Y;

        /// <summary>
        /// Left edge.
        /// </summary>
        public int Left
        {
            get
            {
                return X;
            }
        }

        /// <summary>
        /// Top edge.
        /// </summary>
        public int Top
        {
            get
            {
                return Y;
            }
        }

        /// <summary>
        /// Initialize a new point structure with the specified x and y values.
        /// </summary>
        /// <param name="x">X coordinate.</param>
        /// <param name="y">Y coordinate.</param>
        public Point(int x, int y)
        {
            X = x;
            Y = y;
        }

        /// <summary>
        /// Adds two point structures together.
        /// </summary>
        /// <param name="point1">First point.</param>
        /// <param name="point2">Second point.</param>
        /// <returns>Resulting point.</returns>
        public static Point Add(Point point1, Point point2)
        {
            return new Point(point1.X + point2.X, point1.Y + point2.Y);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="val"></param>
        /// <returns></returns>
        public static implicit operator PointF(Point val)
        {
            return new PointF((float)val.X, (float)val.Y);
        }

    }

    /// <summary>
    /// Float point structure.
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    public struct PointF
    {
        /// <summary>
        /// X coordinate.
        /// </summary>
        public float X;

        /// <summary>
        /// Y coordinate.
        /// </summary>
        public float Y;

        /// <summary>
        /// Left edge.
        /// </summary>
        public float Left
        {
            get
            {
                return X;
            }
        }

        /// <summary>
        /// Top edge.
        /// </summary>
        public float Top
        {
            get
            {
                return Y;
            }
        }

        /// <summary>
        /// Initialize a new floating precision point structure with the specified x and y values.
        /// </summary>
        /// <param name="x">X coordinate.</param>
        /// <param name="y">Y coordinate.</param>
        public PointF(float x, float y)
        {
            X = x;
            Y = y;
        }

        /// <summary>
        /// Adds two point structures together.
        /// </summary>
        /// <param name="point1">First point.</param>
        /// <param name="point2">Second point.</param>
        /// <returns>Resulting point.</returns>
        public static PointF Add(PointF point1, PointF point2)
        {
            return new PointF(point1.X + point2.X, point1.Y + point2.Y);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="val"></param>
        /// <returns></returns>
        public static explicit operator Point(PointF val)
        {
            return new Point((int)val.X, (int)val.Y);
        }

    }

    /// <summary>
    /// Rectangle structure.
    /// </summary>
    [StructLayout(LayoutKind.Explicit)]
    public struct Rectangle
    {
        [FieldOffset(0)]
        private int left;

        [FieldOffset(4)]
        private int top;

        [FieldOffset(8)]
        private int width;

        [FieldOffset(16)]
        private int height;

        [FieldOffset(0)]
        private Point location;

        /// <summary>
        /// Left edge.
        /// </summary>
        public int Left { get { return left; } }
        
        /// <summary>
        /// Top edge.
        /// </summary>
        public int Top { get { return top; } }
        
        /// <summary>
        /// Width.
        /// </summary>
        public int Width { get { return width; } }

        /// <summary>
        /// Height.
        /// </summary>
        public int Height { get { return height; } }

        /// <summary>
        /// Location coordinates.
        /// </summary>
        public Point Location { get { return location; } }

        /// <summary>
        /// Right edge.
        /// </summary>
        public int Right
        {
            get
            {
                return (width + left) - 1;
            }
        }

        /// <summary>
        /// Bottom edge.
        /// </summary>
        public int Bottom
        {
            get
            {
                return (height + top) - 1;
            }
        }

        /// <summary>
        /// X coordinate.
        /// </summary>
        public int X
        {
            get
            {
                return left;
            }
        }

        /// <summary>
        /// Y coordinate.
        /// </summary>
        public int Y
        {
            get
            {
                return top;
            }
        }

        /// <summary>
        /// Initialize a new rectangle structure with the specified left, top, width and height values.
        /// </summary>
        /// <param name="left">The left edge.</param>
        /// <param name="top">The top edge.</param>
        /// <param name="width">Width.</param>
        /// <param name="height">Height.</param>
        public Rectangle(int left, int top, int width, int height)
        {
            this.left = 0;
            this.top = 0;
            this.location = new Point(left, top);
            this.width = width;
            this.height = height;
        }

    }

    /// <summary>
    /// Floating precision rectangle structure.
    /// </summary>
    [StructLayout(LayoutKind.Explicit)]
    public struct RectangleF
    {
        [FieldOffset(0)]
        private float left;

        [FieldOffset(4)]
        private float top;

        [FieldOffset(8)]
        private float width;

        [FieldOffset(16)]
        private float height;

        [FieldOffset(0)]
        private PointF location;

        /// <summary>
        /// Left edge.
        /// </summary>
        public float Left { get { return left; } }
        
        /// <summary>
        /// Top edge.
        /// </summary>
        public float Top { get { return top; } }

        /// <summary>
        /// Width.
        /// </summary>
        public float Width { get { return width; } }
        
        /// <summary>
        /// Height.
        /// </summary>
        public float Height { get { return height; } }

        /// <summary>
        /// Location coordinates.
        /// </summary>
        public PointF Location { get { return location; } }

        /// <summary>
        /// Right edge.
        /// </summary>
        public float Right
        {
            get
            {
                return (width + left) - 1;
            }
        }

        /// <summary>
        /// Bottom edge.
        /// </summary>
        public float Bottom
        {
            get
            {
                return (height + top) - 1;
            }
        }

        /// <summary>
        /// X coordinate.
        /// </summary>
        public float X
        {
            get
            {
                return left;
            }
        }

        /// <summary>
        /// Y coordinate.
        /// </summary>
        public float Y
        {
            get
            {
                return top;
            }
        }

        /// <summary>
        /// Initialize a new floating precision rectangle structure with the specified left, top, width and height values.
        /// </summary>
        /// <param name="left">The left edge.</param>
        /// <param name="top">The top edge.</param>
        /// <param name="width">Width.</param>
        /// <param name="height">Height.</param>
        public RectangleF(float left, float top, float width, float height)
        {
            this.location = new PointF(left, top);
            this.left = left;
            this.top = top;
            this.width = width;
            this.height = height;
        }

    }

    /// <summary>
    /// Polor coordinate formatting flags.
    /// </summary>
    [Flags]
    public enum PolarCoordinateFormatting : int
    {
        /// <summary>
        /// Use default formatting.
        /// </summary>
        Default = 0x0,

        /// <summary>
        /// Use degree symbol.
        /// </summary>
        UseDegreeSymbol = 0x1,

        /// <summary>
        /// Use polar symbol.
        /// </summary>
        UsePolarSymbol = 0x2,

        /// <summary>
        /// Use Pi symbol.
        /// </summary>
        UsePiSymbol = 0x4,

        /// <summary>
        /// Use radian indicator.
        /// </summary>
        UseRadianIndicator = 0x8,

        /// <summary>
        /// Use parenthesis.
        /// </summary>
        UseParenthesis = 0x10,

        /// <summary>
        /// Use brackets.
        /// </summary>
        UseBrackets = 0x20,

        /// <summary>
        /// Display the angle in radians.
        /// </summary>
        DisplayInRadians = 0x40,

        /// <summary>
        /// Standard format.
        /// </summary>
        Standard = 0x1,

        /// <summary>
        /// Standard radians format.
        /// </summary>
        Radians = 0x4 | 0x8 | 0x40,

        /// <summary>
        /// Scientific format.
        /// </summary>
        StandardScientific = 0x2,

        /// <summary>
        /// Scientific radians format.
        /// </summary>
        RadiansScientific = 0x2 | 0x4 | 0x8 | 0x40
    }

    /// <summary>
    /// Polor coordinate structure.
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    public struct PolarCoordinates
    {
        /// <summary>
        /// Polar symbol (φ).
        /// </summary>
        public const char PolarChar = 'φ';

        /// <summary>
        /// Pi symbol (π).
        /// </summary>
        public const char PiChar = 'π';
        
        /// <summary>
        /// Degree symbol (°).
        /// </summary>
        public const char DegreeChar = '°';

        /// <summary>
        /// Default rounding precision for string formatting.
        /// </summary>
        public static int Rounding = 2;

        /// <summary>
        /// Default string formatting flags.
        /// </summary>
        public static PolarCoordinateFormatting Formatting = PolarCoordinateFormatting.Standard;

        private double radius;
        private double angle;

        /// <summary>
        /// Initialize a new polar coordinate structure with the given radius and angle.
        /// </summary>
        /// <param name="radius">The radius.</param>
        /// <param name="angle">The angle.</param>
        public PolarCoordinates(double radius, double angle)
        {
            Formatting = PolarCoordinateFormatting.Standard;
            Rounding = 2;
            this.radius = radius;
            this.angle = angle;
        }

        /// <summary>
        /// Initialize a new polar coordinate structure from another polar coordinate structure.
        /// </summary>
        /// <param name="p">Polar coordinate structure.</param>
        public PolarCoordinates(PolarCoordinates p)
        {
            Formatting = PolarCoordinateFormatting.Standard;
            Rounding = 2;
            radius = p.Radius;
            angle = p.Angle;
        }

        /// <summary>
        /// Angle value.
        /// </summary>
        public double Angle
        {
            get { return angle; }
        }

        /// <summary>
        /// Radius value.
        /// </summary>
        public double Radius
        {
            get { return radius; }
        }

        /// <summary>
        /// Formats the polar coordinates.
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return ToString(PolarCoordinateFormatting.Standard);
        }

        /// <summary>
        /// Formats the polar coordinates.
        /// </summary>
        /// <param name="format">Optional display format to use.</param>
        /// <returns></returns>
        public string ToString(PolarCoordinateFormatting format = PolarCoordinateFormatting.Default)
        {
            PolarCoordinateFormatting f;

            string s = "";
            double a = angle;
            double r = radius;

            f = (format == PolarCoordinateFormatting.Default ? Formatting : format);

            if ((f & PolarCoordinateFormatting.UseParenthesis) != 0)
            {
                s += "(";
            }

            if ((f & PolarCoordinateFormatting.UseBrackets) != 0)
            {
                s += "{";
            }

            s += System.Math.Round(r, Rounding).ToString("0.0") + ", ";

            if ((f & PolarCoordinateFormatting.DisplayInRadians) != 0)
            {
                a *= Polar.Radian;
            }

            s += System.Math.Round(a, Rounding).ToString("0.0");

            if ((f & PolarCoordinateFormatting.UsePiSymbol) != 0)
            {
                s += PiChar;
            }

            if ((f & PolarCoordinateFormatting.UseDegreeSymbol) != 0)
            {
                s += DegreeChar;
            }

            if ((f & PolarCoordinateFormatting.UsePolarSymbol) != 0)
            {
                s += PolarChar;
            }

            if ((f & PolarCoordinateFormatting.UseRadianIndicator) != 0)
            {
                s += " rad";
            }


            if ((f & PolarCoordinateFormatting.UsePiSymbol) != 0)
            {
            }

            if ((f & PolarCoordinateFormatting.UseBrackets) != 0)
            {
                s += "}";
            }

            if ((f & PolarCoordinateFormatting.UseParenthesis) != 0)
            {
                s += ")";
            }

            return s;

        }
        
        /// <summary>
        /// Translate the polar coordinates into screen coordinates with a default rectangle whose center is the center of the described circle with a width and height of 2*radius.
        /// </summary>
        /// <param name="p">Polar coordinates to translate.</param>
        /// <returns></returns>
        public static Point Translate(PolarCoordinates p)
        {
            return Translate(p, new Rectangle(0, 0, (int)((p.Radius * 2) + 1), (int)((p.Radius * 2) + 1)));
        }

        /// <summary>
        /// Translate the polar coordinates into screen coordinates relative to the center of the given rectangle.
        /// </summary>
        /// <param name="p">Polar coordinates to translate.</param>
        /// <param name="rect">Rectangle on which to base translation.</param>
        /// <returns></returns>
        public static Point Translate(PolarCoordinates p, Rectangle rect)
        {
            if (rect.Width < ((p.Radius * 2) + 1) || rect.Height < ((p.Radius * 2) + 1))
            {
                // fit to rectangle
                p = new PolarCoordinates((System.Math.Min(rect.Width, rect.Height) / 2) - 1, p.Angle);
            }

            Point pt = Polar.ToScreenCoordinates(p);

            int x = 0;
            int y = 0;

            Point c = new Point(rect.Width / 2, rect.Height / 2);

            x = c.X + pt.X;
            y = c.Y + pt.Y;

            pt = new Point(x, y);
            return Point.Add(pt, rect.Location);

        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="operand"></param>
        /// <returns></returns>
        public static explicit operator PointF(PolarCoordinates operand)
        {
            return Polar.ToScreenCoordinates(operand);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="operand"></param>
        /// <returns></returns>
        public static explicit operator PolarCoordinates(PointF operand)
        {
            return Polar.ToPolarCoordinates(operand);
        }

    }

    /// <summary>
    /// Polar coordinate math utility.
    /// </summary>
    public static class Polar
    {

        /// <summary>
        /// Radian constant (180/π)
        /// </summary>
        public const double Radian = (180.0 / 3.14159265358979);

        /// <summary>
        /// Convert polar coordinates to cartesean screen-based coordinates.
        /// </summary>
        /// <param name="p">Coordinates to translate.</param>
        /// <returns>A point.</returns>
        public static Point ToScreenCoordinates(PolarCoordinates p)
        {
            return ToScreenCoordinates(p.Radius, p.Angle);
        }

        /// <summary>
        /// Convert polar coordinates to cartesean screen-based coordinates.
        /// </summary>
        /// <param name="a">The angle of the polar coordinate.</param>
        /// <param name="r">The radius of the polar coordinate.</param>
        /// <returns>A point.</returns>
        public static Point ToScreenCoordinates(double r, double a)
        {
            double x = 0;
            double y = 0;

            a /= Radian;

            y = r * System.Math.Cos(a);
            x = r * System.Math.Sin(a);

            return new Point((int)(x), (int)(-y));

        }

        /// <summary>
        /// Convert cartesean screen coordinates into polar coordinates.
        /// </summary>
        /// <param name="p">The point to translate.</param>
        /// <returns>Polar coordinates.</returns>
        public static PolarCoordinates ToPolarCoordinates(PointF p)
        {
            return ToPolarCoordinates(p.X, p.Y);
        }

        /// <summary>
        /// Convert cartesean screen coordinates into polar coordinates.
        /// </summary>
        /// <param name="x">X coordinate.</param>
        /// <param name="y">Y coordinate.</param>
        /// <returns>Polar coordinates.</returns>
        public static PolarCoordinates ToPolarCoordinates(double x, double y)
        {
            double r = 0;
            double a = 0;

            r = System.Math.Sqrt((x * x) + (y * y));

            // screen coordinates are funny, had to reverse this.
            if (y == 0)
            {
                a = 0;
            }
            else
            {
                a = System.Math.Atan(x / y);
            }

            a *= Radian;
            a = (a - 180);
            if (a < 0)
                a = 360 - a;
            if (a > 360)
                a = a - 360;
            
            return new PolarCoordinates(r, a);
        }
        
    }

}
